package top.as.sean.common.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import top.as.sean.common.exception.GlobalException;
import top.as.sean.common.response.RespStatus;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类.
 *
 * @author duanjp
 */
@Slf4j
@Component
public class RedisUtil {

    private static final String UNLOCK_LUA;
    private static RedisTemplate<String, Object> redisTemplate;

    static {
        UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] "
            + "then "
            + "    return redis.call(\"del\",KEYS[1]) "
            + "else "
            + "    return 0 "
            + "end ";
    }

    /**
     * 设置key的有效时间
     *
     * @param key  Redis key
     * @param time 时间长度，单位秒
     * @return boolean
     */
    public static boolean setExpire(String key, long time) {
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 获取Redis key的有效时间
     *
     * @param key Redis key
     * @return long
     */
    public static long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断Redis key 是否有效
     *
     * @param key Redis key
     * @return boolean
     */
    public static boolean isKeyExist(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 批量删除Redis key
     *
     * @param key Redis key
     */
    public static void delete(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(Arrays.asList(key));
            }
        }
    }

    /**
     * 通过key获取redis 缓存数据
     *
     * @param key Redis key
     * @return obj
     */
    public static Object getObject(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 缓存数据到redis 中
     *
     * @param key   key
     * @param value obj value
     */
    public static void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存数据到redis 中并设置生命周期的时间
     *
     * @param key   key
     * @param value obj value
     * @param time  time
     */
    public static void set(String key, String value, long time) {
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            set(key, value);
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public static boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 缓存字符串数据到redis 中
     *
     * @param key   key
     * @param value string value
     */
    public static void setString(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存hash 数据
     *
     * @param key   key
     * @param field key
     * @param value obj value
     */
    public static void hashSet(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 缓存hash 数据,并设置生命周期的时间
     *
     * @param key   key
     * @param field field
     * @param value obj value
     * @param time  time
     */
    public static void hashSet(String key, String field, Object value, Long time) {
        redisTemplate.opsForHash().put(key, field, value);
        redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 缓存hash 数据
     *
     * @param key key
     * @param map map
     */
    public static void hashSet(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 缓存hash 数据,并设置生命周期的时间
     *
     * @param key  key
     * @param map  map
     * @param time time
     */
    public static void hashSet(String key, Map<String, String> map, long time) {
        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 查询缓存hash 数据
     *
     * @param key key
     * @return map
     */
    public static Map<Object, Object> hashGetMap(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取redis hash map 的属性值
     *
     * @param key   Redis key
     * @param field hash map field key
     * @return str
     */
    public static String getHashField(String key, String field) {
        String value = (String) redisTemplate.opsForHash().get(key, field);
        return value;
    }

    /**
     * 统计key 的数量
     *
     * @param keyLike key
     * @return int
     */
    public static Integer keys(String keyLike) {
        Set set = redisTemplate.keys(keyLike);
        if (null == set || set.size() == 0) {
            return 0;
        } else {
            return set.size();
        }
    }

    /**
     * 统计hash key中  field的数量
     *
     * @param key key
     * @return str
     */
    public static Long getHashSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 递增
     *
     * @param key   key
     * @param delta 递增步长
     * @return long
     */
    public static long incr(String key, long delta) {
        if (checkKey(key)) {
            if (delta <= 0) {
                throw new GlobalException(RespStatus.FAIL.getCode(), "delta must be greater than zero");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
        return 0;
    }

    /**
     * 递减
     *
     * @param key   key
     * @param delta 递减步长
     * @return long
     */
    public static long decr(String key, long delta) {
        if (checkKey(key)) {
            if (delta <= 0) {
                throw new GlobalException(RespStatus.FAIL.getCode(), "elta must be greater than zero");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
        return 0;
    }

    /**
     * 判断hash表中item是否存在
     *
     * @param key  key
     * @param item field
     * @return boolean
     */
    public static boolean hHasKey(String key, Object item) {
        if (checkKey(key)) {
            if (null != item) {
                return redisTemplate.opsForHash().hasKey(key, item);
            }
        }
        return false;
    }

    /**
     * 获取分布式锁
     *
     * @param key   key
     * @param value val
     * @param time  单位秒
     * @return boolean
     */
    public static boolean setLock(String key, String value, long time) {
        if (checkKey(key)) {
            RedisCallback<Boolean> callback = (connection) -> connection
                .set(key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8),
                    Expiration.seconds(time), RedisStringCommands.SetOption.SET_IF_ABSENT);
            Boolean res = redisTemplate.execute(callback);
            return res == null ? false : res;
        }
        return false;
    }

    /**
     * 释放分布式锁
     *
     * @param key   key
     * @param value val
     * @return boolean
     */
    public static boolean releaseLock(String key, String value) {
        RedisCallback<Boolean> callback = (connection) -> connection
            .eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, key.getBytes(StandardCharsets.UTF_8),
                value.getBytes(StandardCharsets.UTF_8));
        Boolean res = redisTemplate.execute(callback);
        return res == null ? false : res;
    }

    /**
     * 消息发布
     *
     * @param channel 频道
     * @param msg     消息内容
     */
    public static void publish(String channel, Object msg) {
        redisTemplate.convertAndSend(channel, msg);
    }

    private static boolean checkKey(String key) {
        if (null == key || key.length() == 0) {
            throw new GlobalException(RespStatus.FAIL.getCode(), "key is empty");
        }
        return true;
    }

    /**
     * bitmap设置数据
     *
     * @param key    key
     * @param offset offset
     * @param value  val
     * @return boolean
     */
    public static Boolean setBit(String key, long offset, boolean value) {
        return redisTemplate
            .execute((RedisCallback<Boolean>) connection -> connection.setBit(key.getBytes(), offset, value));
    }

    /**
     * bitmap获取数据
     *
     * @param key    key
     * @param offset offset
     * @return boolean
     */
    public static Boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * bitmap统计计数
     *
     * @param key key
     * @return long
     */
    public static Long bitCount(String key) {
        return redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.bitCount(key.getBytes()));
    }

    /**
     * set集合添加元素
     *
     * @param key   key
     * @param value val
     */
    public static void sAdd(String key, Object... value) {
        redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 获取set集合中的所有元素
     *
     * @param key key
     * @return set
     */
    public static Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 判断元素在set集合中是否存在
     *
     * @param key   key
     * @param value val
     * @return boolean
     */
    public static Boolean isMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 移除集合中的元素
     *
     * @param key   key
     * @param value val
     * @return long
     */
    public static Long remove(String key, Object... value) {
        return redisTemplate.opsForSet().remove(key, value);
    }

    /**
     * 统计set集合中的元素数量
     *
     * @param key key
     * @return long
     */
    public static Long sCount(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    @Resource
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        RedisUtil.redisTemplate = redisTemplate;
    }


}
